package com.le.edu.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import com.amazonaws.HttpMethod;
import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.model.*;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.le.edu.constant.MinioConstant;
import com.le.edu.domain.entity.Section;
import com.le.edu.domain.entity.Video;
import com.le.edu.domain.vo.CreateTaskParam;
import com.le.edu.domain.vo.TaskInfoVo;
import com.le.edu.domain.vo.TaskRecordVo;
import com.le.edu.exception.AppException;
import com.le.edu.mapper.VideoMapper;
import com.le.edu.properties.OssProperties;
import com.le.edu.service.SectionService;
import com.le.edu.service.VideoService;
import lombok.RequiredArgsConstructor;
import org.springframework.http.MediaType;
import org.springframework.http.MediaTypeFactory;
import org.springframework.stereotype.Service;

import java.net.URL;
import java.time.LocalDateTime;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * @author 17447
 * @description 针对表【t_video】的数据库操作Service实现
 * @createDate 2024-04-09 12:35:12
 */
@Service
@RequiredArgsConstructor
public class VideoServiceImpl extends ServiceImpl<VideoMapper, Video>
        implements VideoService {
    private final AmazonS3 amazonS3;
    private final OssProperties properties;
    private final SectionService sectionService;

    @Override
    public TaskInfoVo getTaskInfo(String identifier) {
        Video task = getByIdentifier(identifier);
        if (task == null) {
            return null;
        }
        TaskInfoVo result = TaskInfoVo.builder()
                .finished(true)
                .taskRecord(BeanUtil.copyProperties(task, TaskRecordVo.class))
                .path(getPathUrl(task.getBucketName(), task.getObjectKey()))
                .build();
        boolean doesObjectExist = amazonS3.doesObjectExist(task.getBucketName(), task.getObjectKey());
        if (!doesObjectExist) {
            // 未上传完，返回已上传的分片
//            amazonS3.listMultipartUploads();
            ListPartsRequest listPartsRequest = new ListPartsRequest(task.getBucketName(), task.getObjectKey(), task.getUploadId());
            PartListing partListing = amazonS3.listParts(listPartsRequest);
            result.setFinished(false).getTaskRecord().setExitPartList(partListing.getParts());
        }
        return result;
    }

    @Override
    public Video getByIdentifier(String identifier) {
        return baseMapper.selectOne(new LambdaQueryWrapper<Video>().eq(Video::getFileIdentifier, identifier));
    }

    @Override
    public TaskInfoVo createTask(CreateTaskParam param) {
        String bucketName = properties.getBucketName();
        String fileName = param.getFileName();
        //文件后缀
        String suffix = fileName.substring(fileName.lastIndexOf(".") + 1);
        String key = StrUtil.format("{}/{}.{}",
                DateUtil.format(LocalDateTime.now(), "YYYY/MM/dd"),
                IdUtil.simpleUUID(),
                suffix);
        String contentType = MediaTypeFactory.getMediaType(key).orElse(MediaType.APPLICATION_OCTET_STREAM).toString();
        ObjectMetadata objectMetadata = new ObjectMetadata();
        objectMetadata.setContentType(contentType);
        InitiateMultipartUploadResult initiateMultipartUploadResult = amazonS3
                .initiateMultipartUpload(new InitiateMultipartUploadRequest(bucketName, key)
                        .withObjectMetadata(objectMetadata));
        //大文件分片上传id
        String uploadId = initiateMultipartUploadResult.getUploadId();

        int chunkNum = (int) Math.ceil(param.getTotalSize() * 1.0 / param.getChunkSize());
        //分片信息存储数据库
        Video task = Video.builder()
                .bucketName(bucketName)
                .videoUrl(getPathUrl(bucketName, key))
                .chunkNum(chunkNum)
                .chunkSize(param.getChunkSize())
                .totalSize(param.getTotalSize())
                .fileIdentifier(param.getIdentifier())
                .fileName(fileName)
                .objectKey(key)
                .uploadId(uploadId)
                .build();
        baseMapper.insert(task);
        return TaskInfoVo.builder()
                .finished(false)
                .taskRecord(BeanUtil.copyProperties(task, TaskRecordVo.class))
                .path(getPathUrl(bucketName, key))
                .build();
    }

    @Override
    public String genPreSignUploadUrl(String bucketName, String objectKey, Map<String, String> params) {
        Date currentDate = new Date();
        Date expireDate = DateUtil.offsetMillisecond(currentDate, MinioConstant.PRE_SIGN_URL_EXPIRE.intValue());
        GeneratePresignedUrlRequest request = new GeneratePresignedUrlRequest(bucketName, objectKey)
                .withExpiration(expireDate).withMethod(HttpMethod.PUT);
        if (params != null) {
            params.forEach((key, val) -> request.addRequestParameter(key, val));
        }
        URL preSignedUrl = amazonS3.generatePresignedUrl(request);
        return preSignedUrl.toString();
    }

    @Override
    public String merge(String identifier) {
        Video task = getByIdentifier(identifier);
        if (task == null) {
            throw new RuntimeException("分片任务不存");
        }

        ListPartsRequest listPartsRequest = new ListPartsRequest(task.getBucketName(),
                task.getObjectKey(),
                task.getUploadId());
        PartListing partListing = amazonS3.listParts(listPartsRequest);
        List<PartSummary> parts = partListing.getParts();
        if (!task.getChunkNum().equals(parts.size())) {
            // 已上传分块数量与记录中的数量不对应，不能合并分块
            throw new AppException("分片缺失，请重新上传");
        }
        CompleteMultipartUploadRequest completeMultipartUploadRequest = new CompleteMultipartUploadRequest()
                .withUploadId(task.getUploadId())
                .withKey(task.getObjectKey())
                .withBucketName(task.getBucketName())
                .withPartETags(parts.stream().map(partSummary -> new PartETag(partSummary.getPartNumber(),
                                partSummary.getETag()))
                        .collect(Collectors.toList()));
        CompleteMultipartUploadResult result = amazonS3.completeMultipartUpload(completeMultipartUploadRequest);
        return result.getLocation();
    }

    @Override
    public void removeTableAndVodByIds(List<Long> videoIds) {
        List<Video> videos = baseMapper.selectBatchIds(videoIds);
        // minio删除视频
        DeleteObjectsRequest deleteObjectsRequest = new DeleteObjectsRequest(properties.getBucketName());
        List<DeleteObjectsRequest.KeyVersion> list = videos.stream().map(v -> new DeleteObjectsRequest.KeyVersion(v.getObjectKey())).toList();
        deleteObjectsRequest.setKeys(list);
        amazonS3.deleteObjects(deleteObjectsRequest);
        // 删除数据库
        baseMapper.deleteBatchIds(videoIds);
    }

    @Override
    public void deleteVideo(Video video) {
        Long videoId = video.getVideoId();
        long count = sectionService.count(new LambdaQueryWrapper<Section>()
                .eq(Section::getVideoId, videoId));
        if (count == 0) {
            // 当视频在课时表其他地方使用时删除minio视频 and 数据库内容
            amazonS3.deleteObject(video.getBucketName(), video.getObjectKey());
            baseMapper.deleteById(videoId);
        }
    }

    @Override
    public void removeTableAndVodById(Long videoId) {
        Video video = baseMapper.selectById(videoId);
        // minio删除视频
        amazonS3.deleteObject(video.getBucketName(), video.getObjectKey());
        // 删除数据库
        baseMapper.deleteById(videoId);
    }

    public String getPathUrl(String bucket, String objectKey) {
        return StrUtil.format("{}/{}/{}", properties.getEndpoint(), bucket, objectKey);
    }

}




